探索 Python 的 random 模块。了解伪随机性、播种、生成整数、浮点数、序列以及安全应用程序的最佳实践。
Python Random 模块:深入了解伪随机数生成
在计算世界中,随机性是一个强大而重要的概念。它是从复杂的科学模拟和机器学习模型到视频游戏和安全数据加密等一切事物的引擎。在使用 Python 时,引入这种偶然性元素的主要工具是内置的 random 模块。但是,它提供的“随机性”带有一个关键的警告:它不是真正随机的。它是伪随机。
本综合指南将带您深入了解 Python 的 random
模块。我们将揭开伪随机性的神秘面纱,通过实际示例探索模块的核心功能,最重要的是,讨论何时使用它以及何时为安全敏感的应用程序寻找更强大的工具。无论您是数据科学家、游戏开发者还是软件工程师,对该模块的扎实理解都是您 Python 工具包的基础。
什么是伪随机性?
在我们开始生成数字之前,了解我们正在处理的对象的性质至关重要。计算机是一台确定性机器;它精确地遵循指令。从本质上讲,它无法凭空产生真正的随机数。真正的随机性只能来自不可预测的物理现象,例如大气噪声或放射性衰变。
相反,编程语言使用 伪随机数生成器 (PRNG)。PRNG 是一种复杂的算法,它生成一个看起来是随机的数字序列,但实际上完全由一个称为种子的初始值决定。
- 确定性算法:数字序列由数学公式生成。如果您知道算法和起点,您可以预测序列中的每个数字。
- 种子:这是算法的初始输入。如果您向 PRNG 提供相同的种子,它每次都会生成完全相同的“随机”数字序列。
- 周期:PRNG 生成的数字序列最终会重复。对于一个好的 PRNG 来说,这个周期非常大,对于大多数应用来说实际上是无限的。
Python 的 random
模块使用 梅森旋转算法,这是一种非常流行且强大的 PRNG,具有极长的周期 (219937-1)。它非常适合模拟、统计抽样和游戏,但正如我们稍后将看到的,它的可预测性使其不适合密码学。
播种生成器:可重现性的关键
通过种子控制“随机”序列的能力不是缺陷;这是一个强大的功能。它保证了可重现性,这在科学研究、测试和调试中至关重要。如果您正在运行机器学习实验,您需要确保您的随机权重初始化或数据洗牌每次都相同,以便公平地比较结果。
控制此功能的函数是 random.seed()
。
让我们看看它的实际效果。首先,让我们运行一个未设置种子的脚本:
import random
print(random.random())
print(random.randint(1, 100))
如果您多次运行此代码,每次都会得到不同的结果。这是因为如果您不提供种子,Python 会自动使用来自操作系统的非确定性源(例如当前系统时间)来初始化生成器。
现在,让我们设置一个种子:
import random
# Run 1
random.seed(42)
print("Run 1:")
print(random.random()) # Output: 0.6394267984578837
print(random.randint(1, 100)) # Output: 82
# Run 2
random.seed(42)
print("\nRun 2:")
print(random.random()) # Output: 0.6394267984578837
print(random.randint(1, 100)) # Output: 82
正如您所看到的,通过使用相同的种子初始化生成器(数字 42 是一个传统的选择,但任何整数都可以),我们获得了完全相同的数字序列。这是创建可重现的模拟和实验的基石。
生成数字:整数和浮点数
random
模块提供了一组丰富的功能,用于生成不同类型的数字。
生成整数
-
random.randint(a, b)
这可能是您将使用的最常见的功能。它返回一个随机整数
N
,使得a <= N <= b
。请注意,它包括两个端点。# Simulate a standard six-sided die roll die_roll = random.randint(1, 6) print(f"You rolled a {die_roll}")
-
random.randrange(start, stop[, step])
此函数更加灵活,其行为类似于 Python 的内置
range()
函数。它从range(start, stop, step)
返回一个随机选择的元素。重要的是,它不包括stop
值。# Get a random even number between 0 and 10 (exclusive of 10) even_number = random.randrange(0, 10, 2) # Possible outputs: 0, 2, 4, 6, 8 print(f"A random even number: {even_number}") # Get a random number from 0 to 99 num = random.randrange(100) # Equivalent to random.randrange(0, 100, 1) print(f"A random number from 0-99: {num}")
生成浮点数
-
random.random()
这是最基本的浮点数生成函数。它返回半开区间
[0.0, 1.0)
中的一个随机浮点数。这意味着它可以包括 0.0,但始终小于 1.0。# Generate a random float between 0.0 and 1.0 probability = random.random() print(f"Generated probability: {probability}")
-
random.uniform(a, b)
要在特定范围内获取随机浮点数,请使用
uniform()
。它返回一个随机浮点数N
,使得a <= N <= b
或b <= N <= a
。# Generate a random temperature in Celsius for a simulation temp = random.uniform(15.5, 30.5) print(f"Simulated temperature: {temp:.2f}°C")
-
其他分布
该模块还支持各种其他分布,这些分布模拟真实世界的现象,对于专门的模拟来说非常宝贵:
random.gauss(mu, sigma)
:正态(或高斯)分布,可用于模拟测量误差或智商分数等。random.expovariate(lambd)
:指数分布,通常用于模拟泊松过程中事件之间的时间。random.triangular(low, high, mode)
:三角分布,当您具有最小值、最大值和最可能值时非常有用。
使用序列
通常,您不仅需要一个随机数;您需要从项目集合中进行随机选择或随机重新排序列表。random
模块擅长此操作。
进行选择
-
random.choice(seq)
此函数从非空序列(如列表、元组或字符串)返回一个随机选择的元素。它简单且非常有效。
participants = ["Alice", "Bob", "Charlie", "David", "Eve"] winner = random.choice(participants) print(f"And the winner is... {winner}!") possible_moves = ("rock", "paper", "scissors") computer_move = random.choice(possible_moves) print(f"Computer chose: {computer_move}")
-
random.choices(population, weights=None, k=1)
对于更复杂的场景,
choices()
(复数)允许您从总体中选择多个元素,带有替换。这意味着同一项目可以被选择多次。您还可以指定一个weights
列表,以使某些选择比其他选择更有可能。# Simulate 10 coin flips flips = random.choices(["Heads", "Tails"], k=10) print(flips) # Simulate a weighted dice roll where 6 is three times more likely outcomes = [1, 2, 3, 4, 5, 6] weights = [1, 1, 1, 1, 1, 3] weighted_roll = random.choices(outcomes, weights=weights, k=1)[0] print(f"Weighted roll result: {weighted_roll}")
-
random.sample(population, k)
当您需要从总体中选择多个唯一项目时,请使用
sample()
。它执行无替换的选择。这非常适合用于诸如抽取彩票号码或选择随机项目团队之类的场景。# Select 3 unique numbers for a lottery draw from 1 to 50 lottery_numbers = range(1, 51) winning_numbers = random.sample(lottery_numbers, k=3) print(f"The winning numbers are: {winning_numbers}") # Form a random team of 2 from the participant list team = random.sample(participants, k=2) print(f"The new project team is: {team}")
随机排列序列
-
random.shuffle(x)
此函数用于随机重新排序可变序列(如列表)中的项目。重要的是要记住,
shuffle()
就地修改列表并返回None
。不要犯将其返回值分配给变量的常见错误。# Shuffle a deck of cards cards = ["Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King"] print(f"Original order: {cards}") random.shuffle(cards) print(f"Shuffled order: {cards}") # Incorrect usage: # shuffled_cards = random.shuffle(cards) # This will set shuffled_cards to None!
一个重要的警告:请勿将 `random` 用于密码学或安全性
这是任何专业开发人员最重要的收获。Mersenne Twister PRNG 的可预测性使其对于任何与安全相关的目的都是完全不安全的。如果攻击者可以观察到序列中的几个数字,他们就可以潜在地计算出种子并预测所有后续的“随机”数字。
切勿将 random
模块用于:
- 生成密码、会话令牌或 API 密钥。
- 创建密码散列的 salt。
- 任何加密功能,如生成加密密钥。
- 密码重置机制。
此工作的正确工具:secrets
模块
对于安全敏感的应用程序,Python 提供了 secrets
模块(自 Python 3.6 起可用)。此模块专门设计为使用操作系统提供的最安全的随机性来源。这通常被称为密码安全伪随机数生成器 (CSPRNG)。
以下是如何将其用于常见安全任务:
import secrets
import string
# Generate a secure, 16-byte token in hexadecimal format
api_key = secrets.token_hex(16)
print(f"Secure API Key: {api_key}")
# Generate a secure URL-safe token
password_reset_token = secrets.token_urlsafe(32)
print(f"Password Reset Token: {password_reset_token}")
# Generate a strong, random password
# This creates a password with at least one lowercase, one uppercase, and one digit
-alphabet = string.ascii_letters + string.digits
password = ''.join(secrets.choice(alphabet) for i in range(12))
print(f"Generated Password: {password}")
规则很简单:如果它涉及安全,请使用 secrets
。如果是用于建模、统计或游戏,则 random
是正确的选择。
对于高性能计算:numpy.random
虽然标准 random
模块非常适合通用任务,但它并未针对生成大量数字数组进行优化,这是数据科学、机器学习和科学计算中的常见要求。对于这些应用程序,NumPy 库是行业标准。
numpy.random
模块的性能明显更高,因为它的底层实现是在编译后的 C 代码中。它还旨在与 NumPy 强大的数组对象无缝协作。
让我们比较一下生成一百万个随机浮点数的语法:
import random
import numpy as np
import time
# Using the standard library `random`
start_time = time.time()
random_list = [random.random() for _ in range(1_000_000)]
end_time = time.time()
print(f"Standard 'random' took: {end_time - start_time:.4f} seconds")
# Using NumPy
start_time = time.time()
numpy_array = np.random.rand(1_000_000)
end_time = time.time()
print(f"NumPy 'numpy.random' took: {end_time - start_time:.4f} seconds")
您会发现 NumPy 的速度快几个数量级。它还提供了更广泛的统计分布和用于处理多维数据的工具。
最佳实践和最终想法
让我们总结一下我们旅程中的一些关键最佳实践:
- 播种以实现可重现性:当您需要随机过程可重复时,始终使用
random.seed()
,例如在测试、模拟或机器学习实验中。 - 安全第一:切勿将
random
模块用于与安全或密码学相关的任何事情。请始终改用secrets
模块。这是不可协商的。 - 选择正确的函数:使用最能表达您意图的函数。需要唯一选择?使用
random.sample()
。需要带替换的加权选择?使用random.choices()
。 - 性能至关重要:对于繁重的数值运算,尤其是在处理大型数据集时,请利用
numpy.random
的强大功能和速度。 - 了解就地操作:请注意,
random.shuffle()
会就地修改列表。
结论
Python 的 random
模块是标准库中一个多功能且不可或缺的部分。通过了解其伪随机性质并掌握其用于生成数字和使用序列的核心功能,您可以为您的应用程序添加强大的动态行为层。更重要的是,通过了解其局限性以及何时使用诸如 secrets
或 numpy.random
之类的专用工具,您可以展示专业软件工程师的远见和勤奋。所以,继续吧——自信地模拟、洗牌和选择!